package com.bjy.qa.agent.tools.python;

import com.bjy.qa.agent.exception.MyException;
import com.jcraft.jsch.JSchException;
import com.rslai.commons.ssh.SSH;
import com.rslai.commons.ssh.SSHResponse;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public abstract class PythonHelper {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final String[] dependGlibc = new String[]{
            "GLIBC_2.17",
            "GLIBC_2.16",
            "GLIBC_2.15",
            "GLIBC_2.14",
            "GLIBC_2.13",
            "GLIBC_2.12"
    }; // 依赖的 GLIBC 库（注意：高版本的库要放在前面，高版本安装后低版本就不需要安装了）
    private final String[] dependGlibcUrl = new String[]{
            "http://ftp.gnu.org/gnu/glibc/glibc-2.17.tar.gz",
            "http://ftp.gnu.org/gnu/glibc/glibc-2.16.tar.gz",
            "http://ftp.gnu.org/gnu/glibc/glibc-2.15.tar.gz",
            "http://ftp.gnu.org/gnu/glibc/glibc-2.14.tar.gz",
            "http://ftp.gnu.org/gnu/glibc/glibc-2.13.tar.gz",
            "http://ftp.gnu.org/gnu/glibc/glibc-2.12.tar.gz"
    }; // 依赖的 GLIBC 库的下载位置（注意：高版本的库要放在前面，高版本安装后低版本就不需要安装了）

    private PythonVersion pythonVersion = PythonVersion.PY_3_6_8; // python 版本，默认使用 python3.6.8
    private SSH ssh; // ssh 连接
    protected String home; // 工作目录
    private boolean debug = false; // debug 标记
    private String[] condaConfig = new String[]{}; // conda 的 config 信息（可以添加镜像源或其他 conda 配置）

    /**
     * 构造函数
     * @param condaConfig conda 的 config 信息（可以添加镜像源或其他 conda 配置）
     * @param pythonVersion pyton 版本
     * @param ssh ssh 连接
     * @param home 工作目录
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public PythonHelper(String[] condaConfig, PythonVersion pythonVersion, SSH ssh, String home) throws JSchException, IOException, TimeoutException {
        this.init(condaConfig, pythonVersion, ssh, home, false);
    }

    /**
     * 构造函数
     * @param condaConfig conda 的 config 信息（可以添加镜像源或其他 conda 配置）
     * @param pythonVersion pyton 版本
     * @param ssh ssh 连接
     * @param home 工作目录
     * @param debug 是否开启 debug 模式
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public PythonHelper(String[] condaConfig, PythonVersion pythonVersion, SSH ssh, String home, boolean debug) throws JSchException, IOException, TimeoutException {
        this.init(condaConfig, pythonVersion, ssh, home, debug);
    }

    /**
     * 得到 工作目录
     * @return
     */
    public String getHome() {
        return this.home;
    }

    /**
     * 设置 debug 模式，默认 false
     * @param debug trur: debug 模式, false: 非 debug 模式
     */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    /**
     * log debug 信息
     * @param var1
     * @param var2
     */
    protected void debugLogger(String var1, Object... var2) {
        if (debug) {
            logger.info(var1, var2);
        }
    }

    /**
     * 初始化 PythonHelper
     *      1、保存全局变量
     *      2、进入工作目录
     *      3、检查是否安装 miniconda 没有就安装
     *      4、检查是否安装 python 没有就安装
     * @param condaConfig conda 的 config 信息（可以添加镜像源或其他 conda 配置）
     * @param pythonVersion pyton 版本
     * @param ssh ssh 连接
     * @param home 作目录
     * @param debug 是否开启 debug 模式
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    private boolean init(String[] condaConfig, PythonVersion pythonVersion, SSH ssh, String home, boolean debug) throws JSchException, IOException, TimeoutException {
        this.condaConfig = condaConfig;
        this.pythonVersion = pythonVersion;
        this.ssh = ssh;
        this.debug = debug;
        if (StringUtils.isNotEmpty(home)) {
            this.home = home + "/qap-env-home";
        } else {
            SSHResponse sshResponse = executeCommand("pwd");
            this.home = sshResponse.getBody() + "/qap-env-home";
        }

        executeCommand("export LANG=en_US.utf8 && export LANGUAGE=en_US.utf8"); // 设置系统语言

        enterWorkDir(); // 进入工作目录
        installDependGlibc(); // 安装依赖的 GLIBC 库

        debugLogger("检查是否安装 miniconda");
        SSHResponse sshResponse = executeCommand("if [ -e " + this.home + "/miniconda_4.9.2/bin/conda" + " ]; then echo \"exist\"; fi");
        if (sshResponse.getBody().indexOf("exist") != 0) {
            debugLogger("miniconda 未安装");
            this.installMiniconda();
        } else {
            debugLogger("检测 miniconda 版本号");
            sshResponse = executeCommand(this.home + "/miniconda_4.9.2/bin/conda --version");
            if (!sshResponse.getBody().contains("conda 4.9.2")) {
                debugLogger("miniconda 未安装");
                this.installMiniconda();
            }
        }
        debugLogger("检查是否安装 miniconda，完成");

        setCondaEnv(); // 设置 conda 运行环境
        checkPython(); // 检测 python 环境
        return true;
    }

    /**
     * 进入工作目录
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    private boolean enterWorkDir() throws JSchException, IOException, TimeoutException {
        // 进入工作目录
        debugLogger("进入工作目录 workDir: {}", home);
        SSHResponse sshResponse = executeCommand("mkdir -p " + home);
        if (sshResponse.getBody().contains("Permission denied")) {
            throw new MyException(String.format("进入工作目录失败。执行 %s 命令失败，错误信息：%s", sshResponse.getCmd(), sshResponse.getBody()));
        }
        sshResponse = executeCommand("cd " + home);
        if (sshResponse.getBody().contains("No such file or directory")) {
            throw new MyException(String.format("进入工作目录失败。执行 %s 命令失败，错误信息：%s", sshResponse.getCmd(), sshResponse.getBody()));
        }
        return true;
    }

    /**
     * 安装依赖的 GLIBC 库
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    private boolean installDependGlibc() throws JSchException, IOException, TimeoutException {
        debugLogger("安装 GLIBC");
        while (true) {
            // 遍历找到第一个需要安装的 glibc 库
            String glibc = null;
            String glibcUrl = null;
            SSHResponse sshResponse = executeCommand("strings /lib64/libc.so.6 | grep --color=never GLIBC_");
            for (int i = 0; i < dependGlibc.length; i++) {
                if (!sshResponse.getBody().contains(dependGlibc[i])) {
                    glibc = dependGlibc[i];
                    glibcUrl = dependGlibcUrl[i];
                    break;
                }
            }
            if (glibc == null) {
                break;
            }

            // 安装第一个 glibc 库
            debugLogger("安装 {}", glibc);
            String glibcDir = home + "/GLIBC";

            executeCommand("rm -rf " + glibcDir);
            executeCommand("mkdir -p " + glibcDir);
            executeCommand("cd " + glibcDir);
            executeCommand("wget --quiet --no-check-certificate " + glibcUrl + " -O " + glibcDir + "/glibc.tar.gz");
            executeCommand("tar -xf glibc.tar.gz --strip-components 1");
            executeCommand("mkdir build");
            executeCommand("cd build");
            executeCommand("../configure --quiet --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin");
            executeCommand("make -s");
            executeCommand("make install -s");
            executeCommand("rm -rf " + glibcDir);

            sshResponse = executeCommand("strings /lib64/libc.so.6 | grep --color=never GLIBC_");
            if (!sshResponse.getBody().contains(glibc)) {
                throw new MyException(String.format("%s 安装失败，无法自动处理，请手动安装", glibc));
            }
            debugLogger("安装 {}，完成", glibc);
        }
        debugLogger("安装 GLIBC，完成");

        return true;
    }

    /**
     * 安装 miniconda
     * miniconda 安装包可以从这里下载 https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/?C=M&O=D
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    private boolean installMiniconda() throws JSchException, IOException, TimeoutException {
        debugLogger("安装 miniconda");
        executeCommand("cd " + home);
        executeCommand("rm -rf " + home + "/miniconda_4.9.2");
        executeCommand("rm -rf " + home + "/Miniconda3-py39_4.9.2-Linux-x86_64.sh");
        SSHResponse sshResponse = executeCommand("wget --quiet -P " + home + " https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-py39_4.9.2-Linux-x86_64.sh --no-check-certificate");
        if (StringUtils.isNotEmpty(sshResponse.getBody())) {
            throw new MyException(String.format("安装 miniconda 失败。执行 %s 命令失败，错误信息：%s", sshResponse.getCmd(), sshResponse.getBody()));
        }

        sshResponse = executeCommand("bash " + home + "/Miniconda3-py39_4.9.2-Linux-x86_64.sh -b -u -p " + home + "/miniconda_4.9.2");
        if (!sshResponse.getBody().contains("installation finished")) {
            throw new MyException(String.format("安装 miniconda 失败。执行 %s 命令失败，错误信息：%s", sshResponse.getCmd(), sshResponse.getBody()));
        }
        executeCommand("rm -rf " + home + "/Miniconda3-py39_4.9.2-Linux-x86_64.sh");

        sshResponse = executeCommand(this.home + "/miniconda_4.9.2/bin/conda --version");
        if (!sshResponse.getBody().contains("conda 4.9.2")) {
            throw new MyException(String.format("安装 miniconda 失败。执行 %s 命令失败，错误信息：%s", sshResponse.getCmd(), sshResponse.getBody()));
        }

        setCondaMirrorChannels(); // 设置 conda 镜像源

        debugLogger("安装 miniconda，完成");

        this.createInitCondaSh(); // 创建 conda 初始化文件
        return true;
    }

    /**
     * 设置 conda 镜像源
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    private boolean setCondaMirrorChannels() throws JSchException, IOException, TimeoutException {
        debugLogger("设置 conda 镜像源");
        StringBuffer sb = new StringBuffer();
        sb.append(String.format("%s/miniconda_4.9.2/bin/conda config --set show_channel_urls yes && ", this.home));
        sb.append(String.format("%s/miniconda_4.9.2/bin/conda config --set ssl_verify false && ", this.home));
        for (String config: this.condaConfig) {
            if (StringUtils.isNotBlank(config)) {
                sb.append(String.format("%s/miniconda_4.9.2/bin/conda %s && ", this.home, config));
            }
        }
        sb.append(String.format("%s/miniconda_4.9.2/bin/conda info", this.home));
        executeCommand(sb.toString());
        debugLogger("设置 conda 镜像源，完成");
        return true;
    }

    /**
     * 设置 conda 运行环境
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    private boolean setCondaEnv() throws JSchException, IOException, TimeoutException {
        debugLogger("设置 conda 运行环境");
        executeCommand("__conda_env=\"$('" + this.home + "/miniconda_4.9.2/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\""); // 保存设置 conda 环境信息命令到变量 __conda_env
        executeCommand("eval \"$__conda_env\""); // 执行 conda 环境信息命令
        executeCommand("unset __conda_env"); // 删除变量 __conda_env
        executeCommand("conda deactivate && conda deactivate"); // 退出当前 base 虚拟环境（防止之前系统有 conda 退两次）
        SSHResponse sshResponse = executeCommand("conda --version");
        if (!sshResponse.getBody().contains("conda 4.9.2")) {
            throw new MyException(String.format("设置 conda 运行环境失败。执行 %s 命令失败，错误信息：%s", sshResponse.getCmd(), sshResponse.getBody()));
        }
        debugLogger("设置 conda 运行环境，完成");
        return true;
    }

    /**
     * 创建 conda 初始化文件
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public boolean createInitCondaSh() throws JSchException, IOException, TimeoutException {
        debugLogger("创建 conda 初始化文件");
        StringBuffer sb = new StringBuffer();
        sb.append("######################################## \n");
        sb.append("# \n");
        sb.append("# 初始化 conda 执行： source initCondaEnv.sh \n");
        sb.append("# \n");
        sb.append("######################################## \n");
        sb.append("\n");
        sb.append("__conda_env=\"$('" + this.home + "/miniconda_4.9.2/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\" \n");
        sb.append("eval \"$__conda_env\" \n");
        sb.append("unset __conda_env \n");
        executeCommand(String.format("echo -e \"%s\" > " + this.home + "/initCondaEnv.sh", sb.toString().replace("$", "\\$").replace("\n", "\\n").replace("\"", "\\\"")));
        debugLogger("创建 conda 初始化文件，完成");
        return true;
    }

    /**
     * 检测 python 环境
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public boolean checkPython() throws JSchException, IOException, TimeoutException {
        debugLogger("检测 python 环境");
        if (!isActivatePython()) { // 未激活 python
            if (isCreatePython()) { // 已创建 python，激活
                activatePython();
            } else { // 未创建 python，先创建再激活
                createPython();
                activatePython();
                upgradePip();
            }
        }
        debugLogger("检测 python 环境，成功");
        return true;
    }

    /**
     * 创建 python 虚拟环境
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public boolean createPython() throws JSchException, IOException, TimeoutException {
        debugLogger("创建 python{}", pythonVersion.getName());
        SSHResponse sshResponse = executeCommand("conda create -y -q -k -n py_" + pythonVersion.getName() + "_ python=" + pythonVersion.getName());
        if (!sshResponse.getBody().contains("Preparing transaction:") || !sshResponse.getBody().contains("Verifying transaction:") || !sshResponse.getBody().contains("Executing transaction:")) {
            throw new MyException(String.format("创建 python%s 失败。执行 %s 命令失败，错误信息：%s", pythonVersion.getName(), sshResponse.getCmd(), sshResponse.getBody()));
        }
        debugLogger("创建 python{}，成功", pythonVersion.getName());

        createActivatePythonSh(); // 创建激活 python 文件
        return true;
    }

    /**
     * 创建激活 python 文件
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public boolean createActivatePythonSh() throws JSchException, IOException, TimeoutException {
        debugLogger("创建激活 python 文件");
        StringBuffer sb = new StringBuffer();
        sb.append("######################################## \n");
        sb.append("# \n");
        sb.append(String.format("# 激活 python%s 执行： source activatePython%s.sh \n", pythonVersion.getName(), pythonVersion.getName()));
        sb.append("# \n");
        sb.append("######################################## \n");
        sb.append("\n");
        sb.append("source ./initCondaEnv.sh \n");
        sb.append("conda activate py_" + pythonVersion.getName() + "_");
        executeCommand(String.format("echo -e \"%s\" > " + this.home + "/activatePython%s.sh", sb.toString().replace("$", "\\$").replace("\n", "\\n").replace("\"", "\\\""), pythonVersion.getName()));
        debugLogger("创建激活 python 文件，完成");
        return true;
    }

    /**
     * 是否已创建 python
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public boolean isCreatePython() throws JSchException, IOException, TimeoutException {
        debugLogger("是否已创建 python{} 虚拟环境", pythonVersion.getName());
        SSHResponse sshResponse = executeCommand("conda info --envs");
        if (sshResponse.getBody().contains(this.home + "/miniconda_4.9.2/envs/py_" + pythonVersion.getName() + "_")) {
            debugLogger("是否已创建 python{} 虚拟环境，已创建", pythonVersion.getName());
            return true;
        } else {
            debugLogger("是否已创建 python{} 虚拟环境，未创建", pythonVersion.getName());
            return false;
        }
    }

    /**
     * 激活 python 虚拟环境
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public boolean activatePython() throws JSchException, IOException, TimeoutException {
        debugLogger("激活 python{} 虚拟环境", pythonVersion.getName());
        SSHResponse sshResponse = executeCommand("conda activate py_" + pythonVersion.getName() + "_");
        if (StringUtils.isNotEmpty(sshResponse.getBody())) {
            throw new MyException(String.format("激活 python%s 虚拟环境失败。执行 %s 命令失败，错误信息：%s", pythonVersion.getName(), sshResponse.getCmd(), sshResponse.getBody()));
        }
        if (!isActivatePython()) { // 未激活 python
            throw new MyException(String.format("激活 python%s 虚拟环境失败。执行 %s 命令失败，错误信息：%s", pythonVersion.getName(), sshResponse.getCmd(), sshResponse.getBody()));
        }
        debugLogger("激活 python{} 虚拟环境，成功", pythonVersion.getName());
        return true;
    }

    /**
     * 是否已激活 python
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public boolean isActivatePython() throws JSchException, IOException, TimeoutException {
        debugLogger("是否已激活 python{} 虚拟环境", pythonVersion.getName());
        SSHResponse sshResponse = executeCommand("conda info | grep --color=never \"active environment\"");
        if (sshResponse.getBody().contains("py_" + pythonVersion.getName() + "_")) {
            debugLogger("是否已激活 python{} 虚拟环境，已激活", pythonVersion.getName());
            return true;
        } else {
            debugLogger("是否已激活 python{} 虚拟环境，未激活", pythonVersion.getName());
            return false;
        }
    }

    /**
     * 退出 python 虚拟环境
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public boolean deactivatePython() throws JSchException, IOException, TimeoutException {
        debugLogger("退出 python{} 虚拟环境", pythonVersion.getName());
        SSHResponse sshResponse = executeCommand("conda deactivate");
        if (StringUtils.isNotEmpty(sshResponse.getBody())) {
            throw new MyException(String.format("退出 python%s 虚拟环境失败。执行 %s 命令失败，错误信息：%s", pythonVersion.getName(), sshResponse.getCmd(), sshResponse.getBody()));
        }
        debugLogger("退出 python{} 虚拟环境，成功", pythonVersion.getName());

        return true;
    }

    /**
     * 安装依赖的 python 包
     * @param packageList 需要安装的 python 包数组。例如：{"deepdiff"} 或 {"deepdiff|3.3.0"};
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public boolean installDependPythonPackages(String[] packageList) throws JSchException, IOException, TimeoutException {
        debugLogger("检查必须的 python 包");
        SSHResponse sshResponse = executeCommand("pip list");
        for (String pack : packageList) {
            String[] packArray = pack.split("\\|");
            if (packArray.length > 0) {
                String packName = packArray[0];
                String packVersion = null;
                if (packArray.length > 1) {
                    packVersion = packArray[1];
                }

                Pattern pattern;
                if (StringUtils.isBlank(packVersion)) {
                    pattern = Pattern.compile(packName);
                } else {
                    pattern = Pattern.compile(packName + "\\s++" + packVersion);
                }
                Matcher matcher = pattern.matcher(sshResponse.getBody());
                if (!matcher.find()) {
                    debugLogger(String.format("安装 python packName: %s, version: %s", packName, packVersion));
                    if (StringUtils.isBlank(packVersion)) {
                        sshResponse = executeCommand(String.format("pip install %s", packName));
                        if (!sshResponse.getBody().contains(String.format("Successfully installed %s", packName))) {
                            throw new MyException(String.format("python packName: %s 安装失败，无法自动处理，请手动安装", packName));
                        }
                    } else {
                        sshResponse = executeCommand(String.format("pip install %s==%s", packName, packVersion));
                        if (!sshResponse.getBody().contains(String.format("Successfully installed %s-%s", packName, packVersion))) {
                            throw new MyException(String.format("python packName: %s, version: %s 安装失败，无法自动处理，请手动安装", packName, packVersion));
                        }
                    }
                }
            }
        }
        debugLogger("检查必须的 python 包，完成");
        return true;
    }

    /**
     * 升级 pip
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public abstract boolean upgradePip() throws JSchException, IOException, TimeoutException;

    /**
     * 检测是否安装了 locust
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public abstract boolean checkLocustInstalled() throws JSchException, IOException, TimeoutException;

    /**
     * 安装 locust
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public abstract boolean installLocust() throws JSchException, IOException, TimeoutException;

    /**
     * 执行命令
     * @param command 命令内容
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    public SSHResponse executeCommand(String command) throws JSchException, IOException, TimeoutException {
        SSHResponse sshResponse = ssh.execute(command);
        debugLogger("--> {}", sshResponse.getCmd());
        debugLogger("<-- {}", sshResponse.getBody());
        return sshResponse;
    }
}
